/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package generator

import java.io.InputStream
import java.io.File
import scala.util.parsing.input.PagedSeqReader
import scala.collection.immutable.PagedSeq
import scala.collection.JavaConversions._
import parser._
import parser.analyzer._
import parser.Nodes._
import parser.DataTypes._
import parser.Results._
import util.OptionParser._
import aggr.{SerializedTypes => ST}
import generator.Options._
import com.google.protobuf.CodedInputStream
import com.google.protobuf.Descriptors._
import util.KeyValueList
import util.FileUtils
import util.PBUtils

/**
 * Entry point of local execution.
 */
object Main {

  /**
   * Load new mapper generate by generator.
   */
  def newMapper(fds: List[FileDescriptor], statics: Helpers.StaticVariables): Helpers.Mapper = {
    
    val mapperClassName = "sawzallclone.Mapper"

    val msgs: Array[Descriptor]      = fds.flatMap(PBUtils.getMessages).toArray
    val enums: Array[EnumDescriptor] = fds.flatMap(PBUtils.getEnums).toArray

    val cl = statics.getClass.getClassLoader

    try {
      Class.forName(mapperClassName, true, cl)
           .getConstructor(classOf[Array[Descriptor]], 
                           classOf[Array[EnumDescriptor]], statics.getClass)
           .newInstance(msgs ,enums, statics)
           .asInstanceOf[Helpers.Mapper]
    } catch {
      case e: java.lang.reflect.InvocationTargetException => 
        e.printStackTrace()
        e.getCause().printStackTrace()
        null
    }
  }

  /**
   * Entry point
   */
  def main(args: Array[String]) {

    def usage() = Options.usage("szlcln.sh", "<script-file>")
    val options = try { 
      Options.parse(args)
    } catch {
      case e: OptException =>
      System.err.println(e.getMessage)
      usage()
      exit(1)
    }
    if (options.hasOpt(Help)) {
      usage()
      exit(0)
    }

    if (options.args.isEmpty) {
      System.err.println("No input file.")
      exit(1)
    } 

    val dir = FileUtils.createTempDir(options.hasOpt(LeaveFiles))

    val state = parse(options.args.head, options.hasOpt(DumpTree))

    val (tables, staticvars) = Generator.generate(dir, state)

    val typeMap = ST.makeSTypeMap(state.fds)
    val emitter = new LocalEmitter(typeMap, tables)
    val mapper  = newMapper(state.fds, staticvars)

    if (options.hasOpt(NoInput)) {
        mapper.map(emitter, Helpers.EMPTY_BYTESTRING) // 0 length byte array.
    } else {
      if (options.hasOpt(BytesList)) {
        (new BytesListIterator(System.in)).foreach { v =>
          mapper.map(emitter, v)
        }
      } else {
        (new KeyValueList(System.in)).foreach { kv =>
          mapper.map(emitter, new Helpers.ByteStringWrapper(kv.getValue))
        }
      }
    }

    // Aggregator
    val ret = emitter.invokeTables(options.hasOpt(Text))

    exit(ret)
  }

  /**
   * Parses Sawzall Clone source code.
   *
   * @note This function terminates the process on failure.
   */
  def parse(src: String, dumpTree: Boolean): Analyzer.State = {
    val result = Analyzer.parse(new PagedSeqReader(PagedSeq.fromFile(src)))
    result.msgs.foreach(println)
    result match {
      case Success(state, _) =>
        if (dumpTree) {
          println("==== dump tree ====")
          println(state.ast)
          println("==== dump tree end ====")
        }
        state

      case _: Failure =>
        exit(1)
    }
  }

  /**
   * Makes jar file.
   */
  def makeJarfile(state: Analyzer.State, leaveFiles: Boolean): (File, List[TableValue], Helpers.StaticVariables) = {

    val tmpdir = FileUtils.createTempDir(leaveFiles)

    val (tables, sVars) = Generator.generate(tmpdir, state)

    // Make mapper jarfile.
    val jarfile = File.createTempFile("scuser", ".jar")

    util.Zip.encode(tmpdir, List("sawzallclone"), jarfile)

    println("Jar File = %s".format(jarfile))

    return (jarfile, tables, sVars)
  }
}

/**
 * Iterator to read bytes list from InputStream.
 */
class BytesListIterator(in: InputStream) extends Iterator[Helpers.ByteStringWrapper] {
  private val cin = CodedInputStream.newInstance(in)
  private var nextValue: Helpers.ByteStringWrapper = null

  override def next(): Helpers.ByteStringWrapper = try {
    if (!hasNext()) {
      throw new NoSuchElementException("EOF")
    }
    nextValue
  } finally {
    nextValue = null
  }

  override def hasNext(): Boolean = {
    if (nextValue != null) {
      return true;
    } else {
      if (cin.isAtEnd) {
        return false;
      }
      val b = cin.readBytes()
      cin.resetSizeCounter()

      nextValue = new Helpers.ByteStringWrapper(b)

      return true
    }
  }
}

